﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Xml;
using limpalex.bbparser.Attributes;
using limpalex.bbparser.Converters;
using limpalex.bbparser.Interfaces;
using limpalex.bbparser.Tokens;

namespace limpalex.bbparser
{
    public sealed class Tag
    {
        private class Property
        {
            public Type Converter { get; set; }
            public PropertyInfo PropertyInfo { get; set; }
            public string DefaultValue { get; set; }
        }
        
        public ICollection<Replacement> Replacements { get; set; }

        [Configurable, Converter(typeof(StringCollectionConverter)),DefaultValue("")]
        public ICollection<string> AllowedChildren { get; set; }

        [Configurable,Converter(typeof(StringCollectionConverter)),DefaultValue("")]
        public ICollection<string> AllowedParents { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("true")]
        public bool TextAllowed { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("false")]
        public bool TextRequired { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("false")]
        public bool ChildrenRequired { get; set; }

        [Configurable,Converter(typeof(StringCollectionConverter)),DefaultValue("")]
        public ICollection<string> ParametersRequired { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("true")]
        public bool ParseContent { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("true")]
        public bool UseSmileys { get; set; }

        [Configurable,Converter(typeof(EvaluatorConverter)),DefaultValue("")]
        public IAttributeEvaluator Evaluator { get; set; }

        [Configurable,Converter(typeof(ValidatorConverter)),DefaultValue("")]
        public IAttributeValidator Validator { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("false")]
        public bool ParentRequired { get; set; }

        [Configurable,Converter(typeof(BooleanConverter)),DefaultValue("true")]
        public bool ChildrenAllowed { get; set; }

        private string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            private set
            {
                _name = value.ToLower(TagNameCulture);
            }
        }

        internal static readonly CultureInfo TagNameCulture = new CultureInfo("en-US");

        private static readonly Dictionary<string, Property> ConfigurableProperties;

        private class TagComparer : IEqualityComparer<string>
        {
            public bool Equals(string x, string y)
            {
                return x.Equals(y, StringComparison.OrdinalIgnoreCase);
            }

            public int GetHashCode(string obj)
            {
                return obj.ToUpper().GetHashCode();
            }
        }

        static Tag()
        {
            ConfigurableProperties = new Dictionary<string, Property>(new TagComparer());

            foreach (var property in typeof(Tag).GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                var attributes = new List<object>(property.GetCustomAttributes(false));
                if ( attributes.Exists(obj => obj.GetType() == typeof(ConfigurableAttribute)))
                {
                    var prop = new Property();
                    var converter =
                        (ConverterAttribute) attributes.Find(obj => obj.GetType() == typeof (ConverterAttribute));
                    var defaultValue =
                        (DefaultValueAttribute) attributes.Find(obj => obj.GetType() == typeof (DefaultValueAttribute));
                    prop.Converter = converter.Converter;
                    prop.PropertyInfo = property;
                    prop.DefaultValue = defaultValue.Value;
                    ConfigurableProperties.Add(property.Name, prop);
                }
            }
        }

        private Tag()
        {
            Replacements = new List<Replacement>();
        }

        internal Replacement GetReplacement(TagToken tagToken)
        {
            foreach (var replacement in Replacements)
            {
                var suits = true;

                foreach (var parameter in replacement.GetParameters(false))
                {
                    if ( parameter == "content")
                        continue;
                    if (tagToken.HasAttribute(parameter)) 
                        continue;
                    suits = false;
                    break;
                }

                if (suits)
                    return replacement;
            }

            foreach (var replacement in Replacements)
            {
                var suits = true;

                foreach (var parameter in replacement.GetParameters(true))
                {
                    if (tagToken.HasAttribute(parameter)) 
                        continue;
                    suits = false;
                    break;
                }

                if (suits)
                    return replacement;
            }

            return null;
        }

        public static Tag Create(XmlNode node)
        {
            var result = new Tag {Name = node.Attributes["name"].Value};
            foreach (XmlNode child in node.ChildNodes)
            {
                if (child.NodeType != XmlNodeType.Element)
                    continue;
                if (child.Name == "replacements")
                    result.AddReplacements(child.ChildNodes);
                if (child.Name == "options")
                    result.AddOptions(child.ChildNodes);
            }
            return result;
        }

        private void AddOptions(XmlNodeList nodes)
        {
            AddOptionsDefaultValues();

            foreach (XmlNode node in nodes)
            {
                if (node.NodeType != XmlNodeType.Element)
                    continue;
                var name = node.Name;
                if ( !ConfigurableProperties.ContainsKey(name))
                    continue;
                var property = ConfigurableProperties[name];
                var converter = (IValueConverter)Activator.CreateInstance(property.Converter); 
                property.PropertyInfo.SetValue(this, converter.Convert(node), null);
            }
        }

        private void AddOptionsDefaultValues()
        {
            foreach (var key in ConfigurableProperties.Keys)
            {
                var property = ConfigurableProperties[key];
                var converter = (IValueConverter) Activator.CreateInstance(property.Converter);
                property.PropertyInfo.SetValue(this, converter.ConvertFromString(property.DefaultValue), null);
            }
        }

        private void AddReplacements(XmlNodeList nodes)
        {
            foreach (XmlNode child in nodes)
            {
                if ( child.NodeType != XmlNodeType.Element)
                    continue;
                Replacements.Add(new Replacement(child.InnerText));
            }
        }
    }
}
